package cn.lg.soar.database.query;

import cn.lg.soar.common.model.IPageQuery;
import cn.lg.soar.common.model.PageResult;
import cn.lg.soar.common.util.data.StringUtil;
import cn.lg.soar.common.util.lambda.IGetter;
import cn.lg.soar.common.util.lambda.LambdaUtils;
import cn.lg.soar.database.query.annotations.SoarQueryField;
import cn.lg.soar.database.query.annotations.SoarQueryIgnore;
import cn.lg.soar.database.query.exception.*;
import cn.lg.soar.database.util.PageUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.util.CollectionUtils;

import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 查询包装器
 * @author luguoxiang
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class SqueryWapper<T> {

    private final Class<T> clazz;
    private final Map<String, FieldInfo> mapper;
    private final Map<Class<?>, String> aliasMapper;

    /**
     * @param clazz 返回值类型
     * @param tables 查询相关表信息
     */
    public SqueryWapper(Class<T> clazz, Table<?>...tables) {
        this.clazz = clazz;
        this.aliasMapper = new HashMap<>(tables.length);
        for (Table<?> table : tables) {
            this.aliasMapper.put(table.getEntity(), table.getAlias() + ".");
        }
        // 字段处理
        TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        HashMap<String, FieldInfo> mapper = new HashMap<>();
        String alias = this.aliasMapper.computeIfAbsent(clazz, k -> "");
        for (TableFieldInfo tableFieldInfo : fieldList) {
            Field field = tableFieldInfo.getField();
            SoarQueryIgnore queryIgnore = field.getAnnotation(SoarQueryIgnore.class);
            if (queryIgnore != null) {
                continue;
            }
            String property = tableFieldInfo.getProperty();
            FieldInfo fieldInfo = FieldInfo.createInner(property, alias + tableFieldInfo.getColumn(), tableFieldInfo.getPropertyType());
            mapper.put(property, fieldInfo);
            SoarQueryField queryField = field.getAnnotation(SoarQueryField.class);
            if (queryField == null) {
                fieldInfo.setWhereAble(true);
                fieldInfo.setSelectAble(true);
                fieldInfo.setSortable(true);
                continue;
            }
            fieldInfo.setWhereAble(queryField.whereable());
            fieldInfo.setSelectAble(queryField.querysable());
            fieldInfo.setSortable(queryField.sortable());

            ConditionEnum[] conditions = queryField.conditions();
            if (conditions.length != 0) {
                fieldInfo.setBlacklist(queryField.isBlacklist());
                fieldInfo.setConditions(Arrays.stream(conditions).collect(Collectors.toSet()));
            }
        }
        String keyProperty = tableInfo.getKeyProperty();
        FieldInfo fieldInfo = FieldInfo.create(keyProperty, tableInfo.getKeyColumn(), tableInfo.getKeyType());
        mapper.put(keyProperty, fieldInfo);
        this.mapper = mapper;
    }

    public <E> PageResult<E> query(IService<E> service, IPageQuery pageQuery, QueryWrapper<E> wrapper) {
        PageResult<E> result = new PageResult<>();
        if (pageQuery == null || pageQuery.getPageSize() <= 0) {
            // 不分页
            List<E> list = service.list(wrapper);
            result.setList(list);
            return result;
        }
        result.setPageNo(pageQuery.getPageNo());
        result.setPageSize(pageQuery.getPageSize());
        result.setQueryType(pageQuery.getQueryType());
        if (IPageQuery.QueryType.total.equals(pageQuery.getQueryType())) {
            // 查总记录数
            result.setTotal(service.count(wrapper));
            return result;
        }
        // 分页查数据
        IPage<E> page = PageUtils.toPage(pageQuery);
        page = service.page(page, wrapper);
        result.setTotal(page.getTotal());
        result.setList(page.getRecords());
        return result;
    }

    /**
     * 查询
     * @param service 业务service
     * @param pageQuery 分页参数
     * @param params 过滤参数
     * @param wrapper mp查询包装
     * @return
     * @param <E>
     */
    public <E> PageResult<E> query(IService<E> service, IPageQuery pageQuery, QueryParams params, QueryWrapper<E> wrapper) {
        if (params == null) {
            params = new QueryParams();
        }
        PageResult<E> result = new PageResult<>();
        if (pageQuery == null || pageQuery.getPageSize() <= 0) {
            // 不分页
            setWhere(params.getF(), wrapper);
            setSelect(params.getFields(), wrapper);
            setOrderBy(params.getOrderBy(), wrapper);
            List<E> list = service.list(wrapper);
            result.setList(list);
            return result;
        }
        result.setPageNo(pageQuery.getPageNo());
        result.setPageSize(pageQuery.getPageSize());
        result.setQueryType(pageQuery.getQueryType());
        if (IPageQuery.QueryType.total.equals(pageQuery.getQueryType())) {
            // 查总记录数
            setWhere(params.getF(), wrapper);
            result.setTotal(service.count(wrapper));
            return result;
        }
        // 分页查数据
        IPage<E> page = PageUtils.toPage(pageQuery);

        setWhere(params.getF(), wrapper);
        setSelect(params.getFields(), wrapper);
        setOrderBy(params.getOrderBy(), wrapper);

        page = service.page(page, wrapper);
        result.setTotal(page.getTotal());
        result.setList(page.getRecords());
        return result;
    }

    /**
     * 查询
     * @param service 业务service
     * @param pageQuery 分页参数
     * @param params 过滤参数
     * @return
     * @param <E>
     */
    public <E> PageResult<E> query(IService<E> service, IPageQuery pageQuery, QueryParams params) {
        return query(service, pageQuery, params, Wrappers.query());
    }

    /**
     * 获取list 查询的 QueryWrapper
     * @param params 过滤参数
     * @param wrapper QueryWrapper
     * @return
     * @param <E>
     */
    public <E>QueryWrapper<E> listWrapper(QueryParams params, QueryWrapper<E> wrapper) {
        if (params == null) {
            return wrapper;
        }
        setWhere(params.getF(), wrapper);
        setSelect(params.getFields(), wrapper);
        setOrderBy(params.getOrderBy(), wrapper);
        return wrapper;
    }

    public QueryWrapper<T> listWrapper(QueryParams params) {
        return listWrapper(params, Wrappers.query());
    }

    /**
     * 设置where条件
     * @param filters 过滤条件数组
     * @param wrapper QueryWrapper
     * @param <T>
     */
    public <T>void setWhere(List<String> filters, QueryWrapper<T> wrapper) {
        if (filters == null) {
            return;
        }
        for (String filter : filters) {
            int vs = filter.indexOf(QueryParams.VALUE_JOIN);
            if (vs == -1) {
                throw new FormatException("筛选格式错误，未能找到 “~”：", filter);
            }
            String value = filter.substring(vs + 1);
            String c = filter.substring(0, vs);
            String[] split = c.split(QueryParams.FIELD_JOIN);
            if (split.length != 2) {
                throw new FormatException("筛选格式错误，未能找到  “-”：", filter);
            }
            ConditionEnum condition = ConditionEnum.valueOf(split[1]);
            // 字段处理
            String[] fields = split[0].split("\\|");
            if (fields.length == 1) {
                setWhere(wrapper, fields[0], condition, value);
            } else {
                wrapper.and(x -> {
                    for (String field : fields) {
                        setWhere(x, field, condition, value);
                        x = x.or();
                    }
                });
            }
        }
    }

    protected <T>void setWhere(QueryWrapper<T> wrapper, String field, ConditionEnum condition, String value) {
        FieldInfo fieldInfo = whereMap(field);
        if (!fieldInfo.allow(condition)) {
            throw new FormatException("不允许的条件: ", condition.name());
        }
        String column = fieldInfo.getColumn();
        Class<?> type = fieldInfo.getType();

        String[] values;
        List<Object> collect;
        switch (condition){
            case eq: wrapper.eq(column, valueHandle(type, value));break;
            case ne: wrapper.ne(column, valueHandle(type, value));break;
            case lt: wrapper.lt(column, valueHandle(type, value));break;
            case gt: wrapper.gt(column, valueHandle(type, value));break;
            case le: wrapper.le(column, valueHandle(type, value));break;
            case ge: wrapper.ge(column, valueHandle(type, value));break;
            case like: wrapper.like(column, value);break;
            case likeRight: wrapper.likeRight(column, value);break;
            case likeLeft: wrapper.likeLeft(column, value);break;
            case notLike: wrapper.notLike(column, value);break;
            case in:
                values = StringUtil.split(value, QueryParams.VALUE_JOIN);
                collect = Arrays.stream(values).map(x -> valueHandle(type, x)).collect(Collectors.toList());
                wrapper.in(column, collect);break;
            case notIn:
                values = StringUtil.split(value, QueryParams.VALUE_JOIN);
                collect = Arrays.stream(values).map(x -> valueHandle(type, x)).collect(Collectors.toList());
                wrapper.notIn(column, collect);break;
            case between:
                values = StringUtil.split(value, QueryParams.VALUE_JOIN);
                if (values.length != 2) {
                    throw new FormatException("between 必须要两个值");
                }
                wrapper.between(column, valueHandle(type, values[0]), valueHandle(type, values[1]));break;
            case notBetween:
                values = StringUtil.split(value, QueryParams.VALUE_JOIN);
                if (values.length != 2) {
                    throw new FormatException("notBetween 必须要两个值");
                }
                wrapper.notBetween(column, valueHandle(type, values[0]), valueHandle(type, values[1]));break;
            case isNull: wrapper.isNull(column);break;
            case isNotNull: wrapper.isNotNull(column);break;
            default: throw new FormatException("未知条件: ", condition.name());
        }
    }

    /**
     * 设置 select 参数
     * @param fields 要查询的字段
     * @param wrapper QueryWrapper
     * @param <T>
     */
    public <T>void setSelect(List<String> fields, QueryWrapper<T> wrapper) {
        // 设置查询字段
        if(!CollectionUtils.isEmpty(fields)){
            String[] select = fields.stream().map(this::selectMap).toArray(String[]::new);
            wrapper.select(select);
        }
    }

    /**
     * 设置排序
     * @param orderBys 排序字段
     * @param wrapper QueryWrapper
     * @param <T>
     */
    public <T>void setOrderBy(List<String> orderBys, QueryWrapper<T> wrapper) {
        if (orderBys == null) {
            return;
        }
        // 设置排序字段
        for (String o : orderBys){
            if(o.indexOf(StringUtil.MIDDLE_LINE) == 0){
                wrapper.orderByDesc(orderByMap(o.substring(1)));
            } else {
                wrapper.orderByAsc(orderByMap(o));
            }
        }
    }

    /**
     * 设置映射关系
     * @param param 参数
     * @param column 数据库字段
     * @param type 数据类型
     * @param supports 支持条件
     * @return
     */
    public SqueryWapper<T> setMapper(IGetter<T, ?> param, String column, Class<?> type, MapperSupport...supports) {
        String pName = LambdaUtils.getterToProperty(param);
        FieldInfo fieldInfo = FieldInfo.createInner(pName, column, type);
        this.mapper.put(pName, fieldInfo);
        if (supports.length == 0) {
            fieldInfo.setWhereAble(true);
            fieldInfo.setSelectAble(true);
            fieldInfo.setSortable(true);
        } else {
            for (MapperSupport support : supports) {
                switch (support) {
                    case where: fieldInfo.setWhereAble(true); break;
                    case select: fieldInfo.setSelectAble(true); break;
                    case orderBy: fieldInfo.setSortable(true); break;
                }
            }
        }
        return this;
    }

    /**
     * 设置映射关系
     * @param mapper
     * @return
     */
    public SqueryWapper<T> setMapper(Map<String, FieldInfo> mapper) {
        this.mapper.putAll(mapper);
        return this;
    }

    /**
     * 设置映射关系
     * @param param 参数
     * @param column 数据库字段
     * @param supports 支持条件
     * @return
     * @param <E>
     */
    public <E>SqueryWapper<T> setMapper(IGetter<T, ?> param, IGetter<E, ?> column, MapperSupport...supports) {
        try {
            Method method = column.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(true);
            SerializedLambda lambda = (SerializedLambda) method.invoke(column);
            // 获取字段
            String methodName = lambda.getImplMethodName();
            String prefix;
            if (methodName.startsWith("get")) {
                prefix = "get";
            } else if (methodName.startsWith("is")) {
                prefix = "is";
            } else {
                throw new RuntimeException("无效的getter方法: " + methodName);
            }
            String cName = StringUtil.lowerFirstCase(methodName.replace(prefix, ""));
            // 获取表别名
            String implClass = lambda.getImplClass();
            Class<?> aClass = Class.forName(implClass.replaceAll("/", "."));
            TableInfo tableInfo = TableInfoHelper.getTableInfo(aClass);
            for (TableFieldInfo tableFieldInfo : tableInfo.getFieldList()) {
                if (tableFieldInfo.getProperty().equals(cName)) {
                    cName = tableFieldInfo.getColumn();
                    String alias = this.aliasMapper.computeIfAbsent(aClass, k -> "");
                    cName = alias + cName;
                    return setMapper(param, cName, tableFieldInfo.getPropertyType(), supports);
                }
            }
            if (tableInfo.getKeyProperty().equals(cName)) {
                cName = tableInfo.getKeyColumn();
                String alias = this.aliasMapper.computeIfAbsent(aClass, k -> "");
                cName = alias + cName;
                return setMapper(param, cName, tableInfo.getKeyType(), supports);
            }
        } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        throw new RuntimeException("无法映射数据库字段");
    }

    /**
     * where条件字段映射
     * @param param
     * @return
     */
    protected FieldInfo whereMap(String param) {
        FieldInfo fieldInfo = mapper.get(param);
        if (fieldInfo == null) {
            throw new FieldException("非法字段", param);
        }
        if (fieldInfo.isWhereAble()) {
            return fieldInfo;
        }
        throw new FieldNotWhereException();
    }

    /**
     * select字段映射
     * @param param
     * @return
     */
    protected String selectMap(String param) {
        FieldInfo fieldInfo = mapper.get(param);
        if (fieldInfo == null) {
            throw new FieldException("非法字段", param);
        }
        if (fieldInfo.isSelectAble()) {
            return fieldInfo.getSelectStr();
        }
        throw new FieldUnreadableException();
    }

    /**
     * orderBy字段映射
     * @param param
     * @return
     */
    protected String orderByMap(String param) {
        FieldInfo fieldInfo = mapper.get(param);
        if (fieldInfo == null) {
            throw new FieldException("非法字段", param);
        }
        if (fieldInfo.isSortable()) {
            return fieldInfo.getColumn();
        }
        throw new FieldNotSortException();
    }

    protected Object valueHandle(Class<?> type, String value) {
        if (Number.class.isAssignableFrom(type)) {
            return new BigDecimal(value);
        }
        if (Boolean.class.isAssignableFrom(type)) {
            return "true".equalsIgnoreCase(value) || "1".equals(value);
        }
        return value;
    }

}
